#include "handlers/sessionhandler.h"
#include "utils/Config.h"
#include "utils/logger.h"
#include "main.h"
#include "units/player.h"
#include "units/game.h"
#include <QDataStream>
#include "crypto/loginhelper.h"
#include "crypto/zlibhelper.h"
#include "shared/common.h"
#include "server/clientsocket.h"
#include "units/gamemanager.h"

extern Logg::Logger* _log;
namespace Handlers
{

SessionHandler::SessionHandler(ClientSocket* Socket)
        : _tcpSocket(Socket)
    {
       _handlers[Handlers::Client_Login]        =   &this->Login;
       _handlers[Handlers::Client_UserData]     =   &this->UserData;
       _handlers[Handlers::Client_CreateGame]   =   &this->CreateGame;
       _handlers[Handlers::Client_ServerInfo]   =   &this->ServerInfo;
       _handlers[Handlers::Client_AccInfo]      =   &this->AccInfo;
       _handlers[Handlers::Client_GameList]     =   &this->GameList;
       _handlers[Handlers::Client_GameInfo]     =   &this->GameInfo;
       _handlers[Handlers::Client_Discon]       =   &this->Disconnect;
       _handlers[Handlers::Client_ChangeChar]   =   &this->ChangeChar;
       _handlers[Handlers::Client_GameState]    =   &this->GameState;
       _handlers[Handlers::Client_ConnectPeer]  =   &this->ConnectPeer;
       _handlers[Handlers::Client_JoinGame]     =   &this->JoinGame;
       _handlers[Handlers::Client_Validatekey]  =   &this->Validatekey;
       _handlers[Handlers::Client_LeaveGame]    =   &this->LeaveGame;
       _handlers[Handlers::Client_CloseGame]    =   &this->CloseGame;

       _handlers[Handlers::Both_ping]           =   &this->Ping;
       _handlers[Handlers::Both_unhandled]      =   &this->Unhandled;
       Clients.insert(Socket,this);
    }
    void SessionHandler::InPacket(Packets::TLPacket* pkt)
    {
        //QMutexLocker locker(&mutex);
        if(_handlers.contains(pkt->GetCmd()))
            _handlers[pkt->GetCmd()](pkt, _tcpSocket,this);
        else
           _handlers[Handlers::Both_unhandled](pkt, _tcpSocket,this);
    }
    void SessionHandler::Login(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        QByteArray data;
        QBuffer dev(&data);
        dev.open(QBuffer::ReadWrite);
        QDataStream stm(&dev);
        quint32 sessid=tcpSocket->peerAddress().toIPv4Address() + tcpSocket->peerPort();
        ((SessionHandler*)sender)->setSessID(sessid);
        // 01 00 15 80 73 a3 4d ff 7f 00 00 01 00 00 00 01 00 00 00 00 de 46 00 00
        stm << (quint8) 0x1;      //cmd
        stm << (quint16) 0x15;  //size
        stm << (quint8)0x10 << (quint8)0x31 << (quint8)0x02 << (quint8)0xc1 << (quint8)0xff; //unk data
        stm << (quint8)0x7F << (quint8)0x00 << (quint8)0x00 << (quint8)0x01;// tcpSocket->localAddress().toIPv4Address();
        stm << (quint32) 1;
        stm << (quint32) 0;
        stm << sessid ;
        tcpSocket->Send(data);
    }
    void SessionHandler::UserData(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        //          9f f2 d0 ac
        // /h/BafRenrz8jo7/tCUu oOVTc9uXkJg9/aOtfErhES84uROqZ/c=
        //QString sin("09 00 36 14 79 65 32 70 35 54 6e 73 42 78 63 76 62 6f 77 68 59 68 44 54 20 70 74 75 6c 6c 77 74 77 76 6c 63 75 72 75 6f 6c 63 78 6c 63 6a 6b 64 75 66 6a 69 76 62 79 6a 73");
        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);
        idata.setByteOrder(QDataStream::LittleEndian);
        idata.device()->seek(3);
        quint16 lvl;
        char* acc;
        char *nme;
        quint64 char_type;
        quint32 ip;
        quint16 port;
        quint8 sz;

        idata >> lvl;
        idata >> char_type;
        idata >> ip;
        idata >> port;
        idata >> sz;
        acc = new char[sz+1];memset(acc,0,sz+1);
        idata.readRawData(acc,(int)sz);
        idata >> sz;
        nme = new char[sz+1];memset(nme,0,sz+1);
        idata.readRawData(nme,(int)sz);

        QString salt = database->GetSalt(tr(acc));
        if(!salt.isNull())
        {
            quint32 plid=database->GetAccID(tr(acc));
            Player* pl = new Player(plid,((SessionHandler*)sender)->getSessID(),tr(acc), tr(nme),tcpSocket,lvl,char_type,0);
            ((SessionHandler*)sender)->setPlayerID(plid);
            LPlayers.insert(pl->getSessID(),pl);
            QString outp("Client logged in acc[%1]-char[%2]");
            _log->LogIt(outp.arg(acc).arg(nme),Logg::LINFO);
            emit ((SessionHandler*)sender)->SetPlayer(pl);

            Packets::TLPacket::pheader hd;
            hd.cmd=0x09;
            hd.size=0x36;
            QByteArray bb(salt.toAscii().data(),hd.size);
            Packets::TLPacket opkt(hd,bb);
            tcpSocket->Send(opkt.GetRaw()->data(),opkt.GetRaw()->size());
        }else{
            QString outp("Client failed loggin acc[%1]-char[%2]");
            _log->LogIt(outp.arg(acc).arg(nme),Logg::LINFO);
            QString tt("0B 00 08 01 00 00 00 00 00 00 00");
            Packets::TLPacket opkt(tt);
            tcpSocket->Send(opkt.GetRaw()->data(),opkt.GetRaw()->size());
        }

    }
    void SessionHandler::ServerInfo(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
         QByteArray buf = pkt->GetRaw()->mid(3);
         QString pass(buf.toHex());
        if(database->CheckLogin(((SessionHandler*)sender)->getPlayerID(),pass))
        {
            QByteArray data;
            QBuffer dev(&data);
            dev.open(QBuffer::ReadWrite);
            quint32 ip=GetCorrectIP(tcpSocket->peerAddress().toIPv4Address());//tcpSocket->localAddress().toIPv4Address();
            quint16 port=UDP_PORT;
            /*ip^=((SessionHandler*)sender)->getSessID();
            port^=((SessionHandler*)sender)->getSessID();*/
            QDataStream stm(&dev);
            stm << (quint8)  0x0b;                          //cmd
            stm << (quint16) 0x08;                          //size
            stm << (quint8)  0x0;                           //unk data
            stm << (quint32) ip;
            stm << (quint16) port;
            stm << (quint8) 0;
            tcpSocket->Send(data);
        }else{
            QString tt("0B 00 08 01 00 00 00 00 00 00 00");
            Packets::TLPacket opkt(tt);
            tcpSocket->Send(opkt.GetRaw()->data());
        }
    }
    void SessionHandler::AccInfo(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
         QBuffer buff(pkt->GetRaw());
         buff.open(QBuffer::ReadWrite);
         QDataStream idata(&buff);

        qint8 cmd;
        qint16 size;
        qint32 sessid;


        idata >> cmd;
        idata >> size;
        idata >> sessid;
        SessionHandler* sess = (SessionHandler*)sender;
        Player* pl = LPlayers[sess->getSessID()];
        Player* pl2 = LPlayers[sessid];
        if(pl->getSessID()== pl2->getSessID())
        {
            SWAP(sessid);
        }
        QByteArray data;
        QBuffer dev(&data);
        dev.open(QBuffer::ReadWrite);
        QDataStream odata(&dev);
        odata << (quint8)   0x21;//0x1F;
        odata << (quint16)  0x0000;
        odata << (quint32)  sessid;
        odata << (quint32)  0x72930300;
        odata << (quint8) 0x0;
        odata << (quint8)   pl2->getAcc().size();
        odata.writeRawData(pl2->getAcc().toAscii().data(),pl2->getAcc().size());
        odata.device()->seek(1);
        size=odata.device()->size()-3;
        odata << size;
        tcpSocket->Send(data);
        if(pl->getSessID()== pl2->getSessID())
        {
            SendFList(tcpSocket);
        }
    }
    void SessionHandler::GameList(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        QByteArray data = GameManager::GetPktList();
        if(!data.isEmpty())
        {
            tcpSocket->Send(data);
        }
        //tcpSocket->Send(QByteArray::fromHex("21 00 00"));
    }
    void SessionHandler::GameInfo(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint64 gid;
        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);

        idata.setByteOrder(QDataStream::LittleEndian);
        idata.skipRawData(3);
        idata >> gid;
        Game* gg = GameManager::GetGame(gid);
        if(gg)
        {
            QString dt("140000");
            Packets::TLPacket pktr(dt);
            pktr.getStream()->writeRawData((char*)&gid,8);
            pktr.AddCompressData((void*)gg->getGInfo(),(int)sizeof(pk_GameInfo));
            tcpSocket->Send(pktr.GetRaw()->data(),pktr.GetRaw()->size());
        }
    }
    void SessionHandler::JoinGame(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        //18 00 0C AC CA D7 0B 8F 43 25 DD C0 A8 0C A1

        quint64 gid;
        quint32 pid;
        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);

        idata.setByteOrder(QDataStream::LittleEndian);
        idata.skipRawData(3);
        idata >> gid;
        idata >> pid;
        SWAP(pid);
        GameManager::JoinToGame(gid,pid);
    }
    void SessionHandler::ConnectPeer(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint64 gid;
        quint32 pid;
        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);

        idata.setByteOrder(QDataStream::LittleEndian);
        idata.skipRawData(3);
        idata >> pid;
        idata >> gid;
        SWAP(pid)
        if(LGames.contains(gid) && LPlayers.contains(pid)){

            Game* gg = LGames[gid];
            Player* pl = LPlayers[pid];
            Player* pl2 = LPlayers[((SessionHandler*)sender)->getSessID()];

            quint32 sessid;
            ClientSocket* skt;
            skt     = pl2->GetSkt();
            sessid  = pid;
            Send0x20(skt);
            Send0x16(pl,pl2,sessid,1);

        }else if(gid==0 && LPlayers.contains(pid) && LPlayers[pid]->InGame()){

            Player* pl = LPlayers[pid];
            Player* pl2 = LPlayers[((SessionHandler*)sender)->getSessID()];

            quint32 sessid;
            ClientSocket* skt;
            skt     = pl2->GetSkt();
            sessid  = pid;
            Send0x20(pl->GetSkt());
            Send0x20(skt);
            Send0x16(pl,pl2,sessid,1);

        }
    }
    void SessionHandler::CreateGame(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint64 gid;
        quint16 port;
        quint16 maxlvl;
        quint16 minlvl;
        quint16 maxplayers;
        quint8  gmode;
        quint8 sz;
        char *nme;
        char *desc;
        quint16 bsz;
        char *zchar;
        pk_GameInfo ginfo;

        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);
        idata.setByteOrder(QDataStream::LittleEndian);

        idata.device()->seek(3);
        idata >> gid;
        idata >> port;
        idata >> minlvl;
        idata >> maxlvl;
        idata >> gmode;
        idata >> maxplayers;
        SWAP(maxplayers);
        idata.skipRawData(1);
        idata >> sz;
        nme = new char[sz+1];memset(nme,0,sz+1);
        idata.readRawData(nme,(int)sz);
        idata >> sz;
        desc = new char[sz+1];memset(desc,0,sz+1);
        idata.readRawData(desc,(int)sz);
        idata >> bsz;

        quint32 own = ((SessionHandler*)sender)->getSessID();
        //Game* ngame = new Game(gid,gmode,maxplayers,maxlvl,minlvl,tr(desc),tr(nme),own);
        Game* ngame = GameManager::NewGame(gid,own,gmode,maxplayers,maxlvl,minlvl,tr(nme),tr(desc));
        if(bsz>0)
        {
            zchar = new char[bsz];memset(zchar,0,bsz);
            idata.readRawData(zchar,bsz);
            QByteArray zbuff(zchar,bsz);
            ZLibHelper::Decompress(&zbuff);
            qMemCopy(ngame->getGInfo(),zbuff.data(),sizeof(pk_GameInfo));
        }

        ngame->setGamePort(port);
        if(LPlayers[own]->getUDPort() != port)
                LPlayers[own]->setUDPort(port);

        _log->LogIt(tr("Juego Creado: %1").arg(tr(nme)));
        QString dt("10 00 01 00");
        Packets::TLPacket pkt1(dt);
        tcpSocket->Send(pkt1);
    }
    void SessionHandler::GameState(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint64 gid;
        quint16 port;
        quint16 maxlvl;
        quint16 minlvl;
        quint8  unk;
        quint16 sz;

        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);
        idata.setByteOrder(QDataStream::LittleEndian);
        idata.device()->seek(3);

        idata >> gid;
        idata >> port;
        idata >> unk;
        idata >> maxlvl;
        idata >> minlvl;
        idata >> unk;
        idata >> unk;
        idata >> sz;
        QByteArray zbuff = pkt->GetRaw()->mid(idata.device()->pos());
        if( sz >0 && LGames.contains(gid))
        {
            Game* ngame=LGames[gid];
            ZLibHelper::Decompress(&zbuff);
            qMemCopy(ngame->getGInfo(),zbuff.data(),sizeof(pk_GameInfo));
            Player* pl= LPlayers[ngame->getPlist()->at(0)];
             qMemCopy(ngame->getGInfo()->account,pl->getAcc().toAscii().data(),pl->getAcc().size());
             ngame->getGInfo()->account_id[0]=pl->getSessID();
             if(ngame->getPlist()->count() < ngame->getMaxPlayers() && !ngame->canJoin())
                 ngame->setcanJoin(true);
        }
    }
    void SessionHandler::FriendList(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {

    }
    void SessionHandler::Ping(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        tcpSocket->Send((*pkt));
    }
    void SessionHandler::Validatekey(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {

            quint32 pid;
            QBuffer buff(pkt->GetRaw());
            buff.open(QBuffer::ReadWrite);
            QDataStream idata(&buff);

            idata.setByteOrder(QDataStream::LittleEndian);
            idata.skipRawData(3);
            idata >> pid;
            if(LPlayers.contains(pid))
            {
                /*Player* pl = LPlayers[pid];
                Player* pl2 = LPlayers[((SessionHandler*)sender)->getSessID()];

                Send0x20(pl->GetSkt());
                Send0x16(pl2,pl,pid,0);*/
            }

    }
    void SessionHandler::ChangeChar(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint16 lvl;
        char *nme;
        quint64 char_type;
        quint8 sz;

        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);
        idata.setByteOrder(QDataStream::LittleEndian);
        idata.device()->seek(3);
        idata >> lvl;
        idata >> char_type;
        idata >> sz;

        nme = new char[sz+1];memset(nme,0,sz+1);
        idata.readRawData(nme,(int)sz);
        Player* pl = LPlayers[((SessionHandler*)sender)->getSessID()];
        pl->setChar(char_type);
        pl->setCharName(tr(nme));
        pl->setLvL(lvl);
    }
    void SessionHandler::Disconnect(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        /*tcpSocket->disconnect();
        tcpSocket->waitForDisconnected();
        tcpSocket->close();
        if(LPlayers.contains(pkt->GetSktDesc()))
            LPlayers.remove(pkt->GetSktDesc());*/
    }
    void SessionHandler::LeaveGame(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {
        quint64 gid;
        quint32 pid;
        QBuffer buff(pkt->GetRaw());
        buff.open(QBuffer::ReadWrite);
        QDataStream idata(&buff);

        idata.setByteOrder(QDataStream::LittleEndian);
        idata.skipRawData(3);
        idata >> gid;
        idata >> pid;
        SWAP(gid);
        SWAP(pid);
        GameManager::LeaveGame(pid);
    }
    void SessionHandler::CloseGame(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {

    }
    void SessionHandler::Unhandled(Packets::TLPacket* pkt, ClientSocket* tcpSocket, QObject* sender )
    {

    }
    void SessionHandler::Send0x20(ClientSocket* tcpSocket)
    {
        quint32 dir2=GetCorrectIP(tcpSocket->peerAddress().toIPv4Address());
        quint16 port2=UDP_PORT;

        QByteArray data;
        QBuffer dev(&data);
        dev.open(QBuffer::ReadWrite);
        QDataStream odata(&dev);
        odata << (quint8)   0x22;
        odata << (quint16)  0x0F;
        odata << (quint8)   0x00;
        odata << (quint32)  dir2;
        odata << (quint16)  port2;
        odata << (quint32)  dir2;
        odata << (quint16)  port2;
        odata << (quint16)  0x0;

        tcpSocket->Send(data);
    }
    void SessionHandler::Send0x16(Player* src, Player* dst,quint32 sessid, quint8 flag)
    {
        QString dt("160013");
        Packets::TLPacket pktr(dt);
        quint32 dir;
        quint32 mask=dst->GetSkt()->peerAddress().toIPv4Address()>>8;
        quint32 srvmask=lanIPint>>8;
        quint16 port=0;
        if(mask==srvmask)
        {
            dir=src->GetSkt()->peerAddress().toIPv4Address();
            port=src->getUDPort();
        }else{
            dir=src->GetSkt()->peerAddress().toIPv4Address();
            //GetCorrectIP(src->GetSkt()->peerAddress().toIPv4Address(),dst->GetSkt()->peerAddress().toIPv4Address());
            //dir=GetCorrectIP(dst->GetSkt()->peerAddress().toIPv4Address(),src->GetSkt()->peerAddress().toIPv4Address());
            port=src->getUDPort();
        }



        QString debu = "Connect to peer %1[%2] from %3[%4]";
        QString ipA,ipB;
        ipA.setNum(src->getSessID(),16);
        ipB.setNum(dst->getSessID(),16);
        _log->LogIt(
                    debu.arg(ipA)
                    .arg(src->GetSkt()->peerAddress().toString())
                    .arg(ipB)
                    .arg(dst->GetSkt()->peerAddress().toString())
                    );

        QString nip(src->GetSkt()->peerAddress().toString());

        if(!flag)
        {
           quint8* fb = (quint8*)&dir+3;
           if((*fb)>99 || (*fb) > 9)
               nip=nip.remove(0,1);
           dir=GetCorrectIP(QHostAddress(nip).toIPv4Address(),src->GetSkt()->peerAddress().toIPv4Address());
        }

        SWAP(sessid);
        dir^=sessid;
        port^=sessid;
        SWAP(dir);
        SWAP(port);

        //              intranet dir
        pktr.getStream()->writeRawData((char*)&dir,4);
        pktr.getStream()->writeRawData((char*)&port,2);
        //              wan dir
        pktr.getStream()->writeRawData((char*)&dir,4);
        pktr.getStream()->writeRawData((char*)&port,2);
        (*pktr.getStream()) << (quint16) 0xFFFF;
        pktr.getStream()->writeRawData((char*)&sessid,4);
        (*pktr.getStream()) << (quint8) flag;
        dst->GetSkt()->Send(pktr);
    }
    void SessionHandler::SendFList(ClientSocket* tcpSocket)
    {
        /*
        22 00 02
        03 00
        */
        QByteArray dt=QByteArray::fromHex("22 00 02 01 00");
        tcpSocket->Send(dt);

        //23 00 1B D4 24 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 06 6E 6F 74 77 6F 72 00
        QByteArray data =  QByteArray::fromHex(getTestPacket().toAscii());
                /*QByteArray::fromHex("23 00 1B"
                                              "D4 24 02 00"
                                              "00 00 00 00 00 00 00 00 00 00 00 00 00 00 03"
                                              "06 2D 74 65 73 74 2D 01");*/
        tcpSocket->Send(data);
    }
}

